#include "memory/cache.h"
#include "burst.h"
#include <stdlib.h>
#include <time.h>
#include "cpu/reg.h"
#include <string.h>
#include "common.h"

void dram_write(hwaddr_t addr, size_t len, uint32_t data);
void call_ddr3_read(hwaddr_t addr, void* data);
void call_ddr3_write(hwaddr_t addr, void* data, uint8_t* mask);

//初始化cache
void init_cache(){
    l1_time = l2_time = 0;
    srand(clock());
    int i = 0, j = 0;
    for(i = 0 ;i < l1_sets; i++){
        for(j = 0 ; j < l1_way; j++){
            cache1[i][j].valid = false;
            cache1[i][j].tag = 0;
            memset(cache1[i][j].block, 0 ,CACHE_BLOCK_SIZE);
        }
    }
    for(i = 0; i < l2_sets; i++){
        for(j = 0; j < l2_way; j++){
            cache2[i][j].valid = false;
            cache2[i][j].dirty = false;
            cache2[i][j].tag = 0;
            memset(cache2[i][j].block, 0, CACHE_BLOCK_SIZE);
        }
    }
}

void p_cache_t(){
    printf("(l1: %lu)\t(l2: %lu)\n", l1_time, l2_time);
}

//返回组号
int32_t l1_read(hwaddr_t addr){
    int32_t res = 0;
    int32_t tags = (addr >> (l1_sets_bits + block_bits));
    int32_t set = (addr >> block_bits) & (l1_sets - 1);
    bool hit = false;
    int i;
    for(i = 0; i < l1_way; i++){
        if(cache1[set][i].valid == false) continue;
        if(cache1[set][i].tag == tags){
            hit = true;
            l1_time += 2;
            res = i;
        }
    }
    if(!hit){
        for(i = 0; i < l1_way; i++){
            if(cache1[set][i].valid == false) break;
        }
        if(i == l1_way) i = rand() % l1_way;
        cache1[set][i].valid = true;
        cache1[set][i].tag = tags;
        int32_t l2_set = (addr >> block_bits) & (l2_sets - 1);
        int j = l2_read(addr);
        memcpy(cache1[set][i].block, cache2[l2_set][j].block, CACHE_BLOCK_SIZE);
        res = i;
        l1_time += 200;
    }
    //p_cache_t();
    return res;
}

int32_t l2_read(hwaddr_t addr){
    int32_t tags  = (addr >> (l2_sets_bits + block_bits));
    int32_t set = (addr >> block_bits) & (l2_sets - 1);
    int i;
    bool hit = false;
    int32_t res = 0;
    for(i = 0; i < l2_way; i++){
        if(cache2[set][i].valid == false) continue;
        if(cache2[set][i].tag == tags){
            hit = true;
            l2_time += 2;
            res = i;
        }
    }
    if(!hit){
        for(i = 0; i < l2_way; i++){
            if(cache2[set][i].valid == false) break;
        }
        if(i == l2_way) i = rand() % l2_way;
        if(cache2[set][i].valid && cache2[set][i].dirty){//need to write back
            uint8_t mask[BURST_LEN * 2];
            int j;
            for(j = 0; j < BURST_LEN * 2; j++){
                mask[j] = 1;
            }
            uint32_t address = ((cache2[set][i].tag << (l2_sets_bits + block_bits)) | (set << block_bits));
            //Log("%x", address);
            for(j = 0; j < CACHE_BLOCK_SIZE / BURST_LEN; j++){
                //Log("%x", ((addr >> block_bits) << block_bits) + BURST_LEN * j);
                call_ddr3_write(address + BURST_LEN * j, cache2[set][i].block + BURST_LEN * j, mask);
            }
        }
        cache2[set][i].valid = true;
        cache2[set][i].tag = tags;
        cache2[set][i].dirty = false;
        
        int j;
        //从memory写进cache
        for(j = 0; j < CACHE_BLOCK_SIZE / BURST_LEN; j++){
            //Log("%d %x", j, ((addr >> block_bits) << block_bits) + BURST_LEN * j);
            call_ddr3_read(((addr >> block_bits) << block_bits) + BURST_LEN * j, cache2[set][i].block + BURST_LEN * j);
        }

        res = i;
        l2_time += 200;
    }
    //p_cache_t();
    return res;
}

void l1_write(hwaddr_t addr, size_t len, uint32_t data){
    //Log("%x %d %x", addr, (int)len, data);
    //write through && no write allocate
    int32_t tags = (addr >> (l1_sets_bits + block_bits));
    int32_t sets = (addr >> block_bits) & (l1_sets - 1);
    int32_t offset = (addr & (CACHE_BLOCK_SIZE - 1));

    int i = 0; bool hit = false;
    for(i = 0; i < l1_way; i++){
        if(!cache1[sets][i].valid) continue;
        if(cache1[sets][i].tag == tags){
            hit = true;
            break;
        }
    }
    if(hit){
        if(offset + len <= CACHE_BLOCK_SIZE){//不足一块
            memcpy(cache1[sets][i].block + offset, &data, len);
        }
        else{//至少两块
            memcpy(cache1[sets][i].block + offset, &data, CACHE_BLOCK_SIZE - offset);
            l1_write(addr + CACHE_BLOCK_SIZE - offset, len - (CACHE_BLOCK_SIZE - offset), data >> 8 * (CACHE_BLOCK_SIZE - offset));
        }
        l1_time += 2;
    }
    else l1_time += 200;
    //Log("To l2 write");
    l2_write(addr, len, data);
    //Log("Finish l1 write");
    //p_cache_t();
}

void l2_write(hwaddr_t addr, size_t len, uint32_t data){
    //Log("%x %d %x", addr, (int)len, data);
    //write back && write allocate
    int32_t tags = (addr >> (l2_sets_bits + block_bits));
    int32_t sets = (addr >> block_bits) & (l2_sets - 1);
    int32_t offset = (addr & (CACHE_BLOCK_SIZE - 1));
    int i = 0; bool hit = false;
    for(i = 0; i < l2_way; i++){
        if(!cache2[sets][i].valid) continue;
        if(cache2[sets][i].tag == tags){
            hit = true;
            break;
        }
    }
    if(hit){//write back
        //Log("1");
        cache2[sets][i].dirty = true;
        //Log("%d %d", (int)(offset + len), (int)CACHE_BLOCK_SIZE);
        if(offset + len <= CACHE_BLOCK_SIZE){
            memcpy(cache2[sets][i].block + offset, &data, len);
        }
        else{
            memcpy(cache2[sets][i].block + offset, &data, CACHE_BLOCK_SIZE - offset);
            l2_write(addr + CACHE_BLOCK_SIZE - offset, len - (CACHE_BLOCK_SIZE - offset), data >> 8 * (CACHE_BLOCK_SIZE - offset));
        }
        l1_time += 2;
    }
    else{//write allocate
        //Log("2");
        i = l2_read(addr);
        l2_write(addr, len, data);
    }
    //p_cache_t();
}